package TangramCore;

import java.awt.Dimension;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.Comparator;

public class Dilek extends Polygon2D{

    private Poloha poloha;
    public Skladacka skladacka;
    public Typ typ;
    public int poradi;
    private boolean vyber;
    
    static final int INV_VYBER = 1, INV_ZORDER=2, INV_POLOHA = 4, INV_VALID = 0;
    private int invalidate, invalidateZ;

    public static class Typ {
        public int id;
        public String nazev;
        public int pocetStran;
        public int pocetVyskytu; //pocet vyskytu ve skladacce
        public double vrcholy[][];
        public int deleni[];
        public int rotationSymetrySides; //pocet stran po jejichz otoceni je dilek symetricky
        public double rotationSymetryAngle; //uhel po jehoz otoceni je dilek symetricky;
        public boolean flipSymetry; //zda je dilek osove soumerny
        public double flipSymetryAngle; // uhel, o kolik se po preklopeni kolem osy y ze zakladni polohy musi dilek natocit, aby zaujal stejnou polohu
        public Obrys oDefault;
        public Side sidesSortedLength[], sidesSortedAngle[];
        public Polygon2D pOrig;
        
        public Typ(int id, String nazev, int pocetStran, int pocetVyskytu) {
            this.id = id;
            this.nazev = nazev;
            this.pocetStran = pocetStran;
            this.pocetVyskytu = pocetVyskytu;
            flipSymetry = false;
            flipSymetryAngle = 0;
            rotationSymetrySides = pocetStran;
            rotationSymetryAngle = 2 * Math.PI;
        }
        
        Point2D[] newPoints() {
            Point2D p[] = new Point2D[pocetStran];
            for (int i = 0; i < pocetStran; i++)
                p[i] = new Point2D(vrcholy[i][0], vrcholy[i][1]);
            return p;
        }
        
        void setPoints(Point2D[] p) {
            for (int i = 0; i < pocetStran; i++)
                p[i].setLocation(vrcholy[i][0], vrcholy[i][1]);
        }
        
        public void getDefaultSortedSides() {
            sidesSortedLength = new Side[rotationSymetrySides];
            sidesSortedAngle = new Side[rotationSymetrySides];
            Obrys.ObrysData od = oDefault.od;
            Point2D p1 = od.segCoords[od.nSegs-2], p2;
            for(int i=0; i< rotationSymetrySides && i < od.segCoords.length; i++){
                p2 = od.segCoords[i];
                sidesSortedLength[i] = new Side(p1 , p2, od.segLength[i+1], od.insideAngle[i]);
                sidesSortedAngle[i] = sidesSortedLength[i];
                p1 = p2;
            }
            Arrays.sort(sidesSortedLength, new SideLengthComparator<Side>());
            Arrays.sort(sidesSortedAngle, new SideAngleComparator<Side>());
        }
    }
   
    public Dilek(Dilek d){
        this(d.skladacka, d.poradi, d.typ, d.poloha);
    }
    
    public Dilek(Skladacka s, int por, Typ t) {
        super(t.pOrig);
        skladacka = s;
        poradi = por;
        poloha = new Poloha();
        poloha.zOrder = poradi;
        typ = t;
        vyber = false;
        invalidate = INV_VYBER;
        invalidateZ = INV_VALID;
    }
    
    public Dilek(Skladacka s, int por, Typ t, Poloha p) {
        super(t.pOrig);
        skladacka = s;
        poradi = por;
        poloha = new Poloha();
        poloha.copyFrom(p);
        typ = t;
        vyber = false;
        flip();
        opravOtoceni(0);
        //debugOtoceni();
        rotate(poloha.otoceni);
        translate(poloha.posunX, poloha.posunY);
        invalidate = invalidateZ = 0;
    }
    
    public void debugOtoceni(){
        System.out.println(poloha.otoceni/Math.PI*180);
    }
    
    public void copyFrom(Dilek d){
        super.copyFrom(d);
        invalidate += (poloha.equals(d.poloha) ? 0 : INV_POLOHA);
        invalidateZ +=  ((poloha.zOrder == d.poloha.zOrder) ? 0 : Dilek.INV_ZORDER);
        poloha.copyFrom(d.poloha);
    }
    
    public Poloha getPoloha(){
        return poloha;
    }
    
    public void setPoloha(Poloha p){
        
    }
    
    private void flip(){
        if(poloha.flip)
            for(Point2D p: points)
                p.x = -p.x;
    }
    
    public int vyber(boolean jak) {
        if (jak != vyber){
            invalidate += (vyber = jak) ? INV_VYBER: -INV_VYBER;
            return vyber ? 1 : -1;
        }
        return 0;
    }
    
    public int invertujVyber() {
        invalidate += (vyber = !vyber) ? INV_VYBER: -INV_VYBER;
        return vyber ? 1 : -1;
    }
    
    public boolean jeVybran() {
        return vyber;
    }
    
    public boolean posun(double dx, double dy){
        poloha.posunX += dx;
        poloha.posunY += dy;
        translate(dx, dy);
        invalidate += INV_POLOHA;
        return true;
    }
    
    public boolean setPosun(double x, double y){
        double dx = x - poloha.posunX;
        double dy = y - poloha.posunY;
        poloha.posunX += dx;
        poloha.posunY += dy;
        translate(dx, dy);
        invalidate += INV_POLOHA;
        return true;
    }
    
    public boolean setPosun(Point2D p){
        return setPosun(p.x, p.y);
    }
    
    public boolean posun(Point2D p) {
        if (p.x == 0 && p.y == 0)
            return false;
        return posun(p.x, p.y);
    }
    public static boolean posunAll(Dilek[] d, double dx, double dy){
        boolean ret = false;
        for(Dilek dil: d)
            ret = dil.posun(dx, dy);
        return ret;
    }
    public static boolean posunAll(Dilek[] d, Point2D p){
        boolean ret = false;;
        for(Dilek dil: d)
            ret = dil.posun(p.x, p.y);
        return ret;
    }
    
    void opravOtoceni(double a){
        poloha.otoceni = (poloha.otoceni + a) % (2 * Math.PI);
        if(poloha.otoceni<0)
            poloha.otoceni += 2 * Math.PI;
        if(!skladacka.SMART_ROTATION){
            poloha.otoceni = Math.round(poloha.otoceni / skladacka.ROTACE_KROK) * skladacka.ROTACE_KROK;
            //TODO zvazit zda provadet normalizaci otoceni pro kazdem natoceni
            //double mod = Math.round(typ.rotationSymetryAngle / skladacka.ROTACE_KROK);
            //poloha.otoceni = (Math.round(poloha.otoceni / skladacka.ROTACE_KROK) % mod) * skladacka.ROTACE_KROK;
        }
    }
    public void otoc(double a) {
        super.copyFrom(typ.pOrig);
        flip();
        opravOtoceni(a);
        rotate(poloha.otoceni);
        translate(poloha.posunX, poloha.posunY);
        invalidate += INV_POLOHA;
        //debugOtoceni();
    }
    
    public void normalize(){
        if(typ.flipSymetry && poloha.flip){
            poloha.flip = false;
            poloha.otoceni += typ.flipSymetryAngle;
        }
        if(!skladacka.SMART_ROTATION){
            double mod = Math.round(typ.rotationSymetryAngle / skladacka.ROTACE_KROK);
            poloha.otoceni = (Math.round(poloha.otoceni / skladacka.ROTACE_KROK) % mod) * skladacka.ROTACE_KROK;
        } else
            while(poloha.otoceni > typ.rotationSymetryAngle)
                poloha.otoceni -= typ.rotationSymetryAngle;
        //  poloha.otoceni -= typ.rotationSymetryAngle;
        //opravOtoceni(0);
        //while(poloha.otoceni > typ.rotationSymetryAngle)
        //  poloha.otoceni -= typ.rotationSymetryAngle;
        super.copyFrom(typ.pOrig);
        flip();
        opravOtoceni(0);
        rotate(poloha.otoceni);
        translate(poloha.posunX, poloha.posunY);
        invalidate += INV_POLOHA;
    }
    
    public void otoc(Point2D p, double a) {
        super.copyFrom(typ.pOrig);
        flip();
        opravOtoceni(a);
        rotate(poloha.otoceni);
        double dx = poloha.posunX - p.x, dy = poloha.posunY - p.y;
        Point2D p3 = new Point2D(dx, dy);
        p3.rotate(a);
        poloha.posunX += p3.x - dx;
        poloha.posunY += p3.y - dy;
        translate(poloha.posunX, poloha.posunY);
        invalidate += INV_POLOHA;
        //debugOtoceni();
    }
    
    public void flip(Point2D pivot) {
        double px = 0;
        if(pivot != null){
            px = pivot.x;
        }
        for(Point2D p: points){
            p.x -= px;
            p.x = -p.x;
            p.x += px;
        }
        poloha.otoceni = 2*Math.PI - poloha.otoceni;
        poloha.posunX -= px;
        poloha.posunX = -poloha.posunX;
        poloha.posunX += px;
        poloha.flip = !poloha.flip;
        invalidate += INV_POLOHA;
    }
    
    public void otocStupne(double a) {
        otoc(Math.PI*a/180);
    }
    
    Point2D getPivot() {
        return new Point2D(poloha.posunX, poloha.posunY);
    }
    
    public double [] getUhly(){
        double ret [] = new double[points.length];
        Point2D p1 = new Point2D(points[points.length-1]);
        for(int i=0; i<points.length; i++){
            double d = p1.distance(points[i]);
            ret[i] = Math.asin((points[i].y-p1.y)/d);
            if((points[i].x-p1.x)<0)
                ret[i] = Math.PI - ret[i];
            p1.copyFrom(points[i]);
        }
        return ret;
    }
    
    public int getSidesCount(){
        return points.length;
    }
 
    public boolean intersects(double x1, double y1, double x2, double y2) {
        Point2D p[] = {
            new Point2D(x1, y1),
            new Point2D(x2, y2)
        };
        return intersects(new Polygon2D(p));
    }
    
    public void getNearestPoint(Point2D p, Point2D ret, boolean stred) {
        int deleni[] = typ.deleni;
        if (stred)
            Vect.kratsiVect(ret, new Point2D(p.x-poloha.posunX,p.y-poloha.posunY));
        for (int i = 0; i < points.length; i++) {
            Vect.kratsiVect(ret, Vect.vect(p, points[i]));
            for (int j = 1; j < deleni[i]; j++)
                Vect.kratsiVect(ret, Vect.vect(p, Vect.vectCast(points[i], points[(i+1)%points.length],(double)j / deleni[i])));
        }
    }
    
    public void getVectNearest(Dilek d, Point2D ret, boolean stred) {
        int deleni[] = typ.deleni;
        for (int i = 0; i < points.length; i++) {
            d.getNearestPoint(points[i], ret, stred);
            for (int j = 1; j < deleni[i]; j++)
                d.getNearestPoint(Vect.vectCast(points[i], points[ (i + 1) % points.length], (double) j / deleni[i]), ret, stred);
        }
    }
    
    public void getVectNearestTwoSides(Dilek d,
            Point2D pbod1, Point2D pret1, Point2D[] pstr1,
            Point2D bod1, Point2D ret1, Point2D[] str1,
            Point2D pbod2, Point2D pret2, Point2D[] pstr2,
            Point2D bod2, Point2D ret2, Point2D[] str2
    ) {
        for(int i = 0; i < points.length; i++)
            d.getNearestSide(points[i], pbod1, pret1, pstr1, bod1, ret1, str1);
        for(int i = 0; i < d.points.length; i++)
            getNearestSide(d.points[i], pbod2, pret2, pstr2, bod2, ret2, str2);
    }
    
    public void getVectNearestSide(Dilek d, Point2D ret, Point2D ret2){
        for(int i = 0; i < points.length; i++)
            d.getNearestSide(points[i], ret);
        for(int i = 0; i < d.points.length; i++)
            getNearestSide(d.points[i], ret2);
    }
    
    public void getNearestSide(Point2D c, Point2D pbod, Point2D pret, Point2D[] pstr,  Point2D bod, Point2D ret, Point2D[] str){
        Point2D a = points[points.length-1], b, tmp = new Point2D(), A = null, B = null, C = null;
        Point2D nej = new Point2D(Double.MAX_VALUE, Double.MAX_VALUE);
        double X, Y, t;
        for(int i = 0; i<points.length; i++){
            b = points[i];
            X = b.x - a.x;
            Y = b.y - a.y;
            if(X == 0)
                t = (c.y - a.y) / Y;
            else if (Y == 0)
                t = (c.x - a.x) / X;
            else
                t = (X*(c.x - a.x) - Y*(a.y - c.y)) / (X*X + Y*Y);
            if(t >= 0 && t <= 1){
                tmp.setLocation(a.x + t*X - c.x, a.y + t*Y - c.y);
                //Point2D ptmp = new Point2D(ret);
                if(Vect.kratsiVect(nej, tmp)){
                    A = a;
                    B = b;
                    C = c;
                }
                /*if(Vect.kratsiVect(ret, tmp) || pstr[0] == null){
                    if(zmena == false){
                        pret.copyFrom(ptmp);
                        pstr[0] = str[0];
                        pstr[1] = str[1];
                    }
                    str[0] = a;
                    str[1] = b;
                    zmena = pstr[0] != null;
                }*/
            }
            a = b;
        }
        Point2D ptmp = new Point2D(ret);
        if(Vect.kratsiVect(ret, nej)){
            pret.copyFrom(ptmp);
            pbod.copyFrom(bod);
            pstr[0] = str[0];
            pstr[1] = str[1];
            str[0] = A;
            str[1] = B;
            bod.copyFrom(C);
        }
        else if(Vect.kratsiVect(pret, nej)){
            pstr[0] = A;
            pstr[1] = B;
            pbod.copyFrom(C);
        }
    }
    
    public void getNearestSide(Point2D c, Point2D ret){
        //getNearestSide(c, ret, null);
        Point2D a = points[points.length-1], b, tmp = new Point2D();
        double X, Y, t;
        for(int i = 0; i<points.length; i++){
            b = points[i];
            X = b.x - a.x;
            Y = b.y - a.y;
            if(X == 0)
                t = (c.y - a.y) / Y;
            else if (Y == 0)
                t = (c.x - a.x) / X;
            else
                t = (X*(c.x - a.x) - Y*(a.y - c.y)) / (X*X + Y*Y);
            if(t >= 0 && t <= 1){
                tmp.setLocation(a.x + t*X - c.x, a.y + t*Y - c.y);
                Vect.kratsiVect(ret, tmp);
            }
            a = b;
        }
    }
    
    static class Validity implements Comparable<Validity>{
        Dilek d;
        private Rectangle2D rect = null;
        private int invalidate, invalidateZ;
        
        Validity(Dilek d) {
            this.d = d;
            invalidate = d.invalidate - Dilek.INV_VYBER;
            invalidateZ = d.invalidateZ - Dilek.INV_ZORDER;
            rect = new Rectangle2D.Double(0,0,0,0);//o.getBounds2D();
        }
        
        static Validity[] getAll(Dilek d[]) {
            Validity v[] = new Validity[d.length];
            for (int i = 0; i < v.length; i++)
                v[i] = new Validity(d[i]);
            return v;
        }
        
        public int compareTo(Validity o){
            Integer zthis = d.poloha.zOrder, zo = o.d.poloha.zOrder;
            return zthis.compareTo(zo);
        }
        
        Rectangle2D getLastValid() {
            return rect;
        }
        
        int validate() {
            int i = d.invalidate - invalidate;
            invalidate += i; //uz nikdy nepujde ziskat rectangle !
            invalidateZ = d.invalidateZ;
            return i;
        }
        
        int validate(boolean cleared, Rectangle2D r) {
            int i = d.invalidate - invalidate;
            int iz = d.invalidateZ - invalidateZ;
            if (cleared) {
                r.setRect(d.getBounds2D(rect));
            } else {
                if (i == Dilek.INV_VALID && iz <= Dilek.INV_VALID)
                    r.setRect(0,0,0,0);
                else if (Math.abs(i) > Dilek.INV_VYBER) {
                    r.setRect(rect);
                    d.getBounds2D(rect);
                    r.add(rect);
                } else
                    r.setRect(d.getBounds2D(rect));
            }
            invalidate += i;
            invalidateZ += iz;
            return iz;
        }
        
        void change(Dilek d){
            this.d = d;
            invalidate = d.invalidate - Dilek.INV_POLOHA;
            invalidateZ = d.invalidateZ - Dilek.INV_ZORDER;
        }
    }
    
    public static void copyFrom(Dilek dest[],Dilek src[]){
        if(dest == null)
            return;
        for(int i = 0; i < src.length; i++)
            dest[i].copyFrom(src[i]);
    }
    
    public void setZOrder(int z){
        if(poloha.zOrder == z)
            return;
        poloha.zOrder = z;
        invalidateZ += INV_ZORDER;
    }
    public void changeZOrder(int dz){
        poloha.zOrder += dz;
        invalidateZ += INV_ZORDER;
    }
    
    public static Rectangle2D getBoundsAll(Dilek d[]){
        Rectangle2D r = new Rectangle2D.Double();
        for(Dilek dil: d)
            Hladina.addRect2D(r, dil.getBounds2D());
        return r;
    }
    
    public static Rectangle2D getBoundsAll(Dilek d[],boolean vyber){
        Rectangle2D r = new Rectangle2D.Double();
        for (Dilek dil: d)
            if(dil.vyber == vyber)
                Hladina.addRect2D(r, dil.getBounds2D());
        return r;
    }
    
    public static Point2D getCenter(Dilek d[]) {
        Rectangle2D r=getBoundsAll(d);
        return new Point2D(r.getCenterX(), r.getCenterY());
    }
    
    public Point2D getCenter(){
        return new Point2D(poloha.posunX, poloha.posunY);
    }
    
    public static void vycentruj(Dilek d[]) {
        Dimension s = new Dimension(Skladacka.MAXCOORD,Skladacka.MAXCOORD);//getSize();
        Point2D p = getCenter(d);
        p.x = s.width / 2 - p.x;
        p.y = s.height / 2 - p.y;
        for(Dilek dil: d)
            dil.posun(p);
    }
    public static void flipAll(Dilek d[], Point2D p){
        for(Dilek dil: d)
            dil.flip(p);
    }
    
    public static int vyberVse(Dilek d[], boolean jak){
        for(Dilek dil: d)
            dil.vyber(jak);
        return jak ? d.length : 0;
    }
    
    public static void normalizeAll(Dilek d[]){
        for(Dilek dil: d)
            dil.normalize();
    }
    public static class Side{
        public Point2D p1, p2;
        public double length, angle;
        //public int duplicity;
        public Side(Point2D p1, Point2D p2){
            this.p1 = p1;
            this.p2 = p2;
            length = p1.distance(p2);
//      duplicity = d;
        }
        public Side(Point2D p1, Point2D p2, double length, double angle){
            this.p1 = p1;
            this.p2 = p2;
            this.length = length;
            this.angle = angle;
        }
    }
    
    static class SideLengthComparator<T extends Side> implements Comparator<T> {
        public int compare(T o1, T o2){
            Side s1 = o1, s2 = o2;
            if(s1.length </*>*/ s2.length)
                return -1;
            if(s1.length >/*<*/ s2.length)
                return 1;
            return 0;
        }
    }
    
    static class SideAngleComparator<T extends Side> implements Comparator<T> {
        public int compare(T o1, T o2){
            Side s1 = o1, s2 = o2;
            if(s1.angle > s2.angle)
                return -1;
            if(s1.angle < s2.angle)
                return 1;
            return 0;
        }
    }
    
    private Side[] createSortedSides(Point2D p[], int dupmod){
        Side side[] = new Side[dupmod];
        Point2D p1 = p[p.length-1], p2;
        for(int i=0; i< dupmod && i < p.length; i++){
            p2 = p[i];
            side[i] = new Side(p1 , p2);
            p1 = p2;
        }
        Arrays.sort(side, new ReverseComparator<Side>(new SideLengthComparator<Side>()));
        return side;
    }
    
    public Side[] getSortedSides(){
        return createSortedSides(points, typ.rotationSymetrySides);
    }
}
